package com.csun.dsms.comment.core.aspect;

import com.csun.dsms.comment.base.constant.GlobalConstant;
import com.csun.dsms.comment.core.annotation.LogAnnotation;
import com.csun.dsms.comment.core.utils.RequestUtil;
import com.csun.dsms.model.domain.DsmsLog;
import com.csun.dsms.model.domain.DsmsUser;
import com.csun.dsms.service.DsmsLogService;
import com.csun.dsms.utils.JacksonUtil;
import com.csun.dsms.utils.PublicUtil;
import com.csun.dsms.utils.RedisUtil;
import com.google.gson.Gson;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author puyijun
 * @date  2017.12.20
 * @desc 日志记录AOP实现
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Resource
    private RedisUtil redisUtil;
    @Resource
    private DsmsLogService dsmsLogService;
    @Resource
    private TaskExecutor taskExecutor;

    private static final int MAX_SIZE = 2000;
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());
	  
    private String requestPath = null ;         // 请求地址
    private String ipAddr = "";
    private String userName = null ;            // 用户名
    private String authorization = null;        // 认证信息
    private Map<?,?> inputParamMap = null ;     // 传入参数
    private Map<String, Object> outputParamMap = null;  // 存放输出结果
    private long startTimeMillis = 0;           // 开始时间
    private long endTimeMillis = 0;             // 结束时间
  
    /** 
     * @Description: 方法调用前触发  记录开始时间  
     * @param joinPoint 
     */  
    @Before("execution(* com.csun.dsms.controller..*.*(..))")
    public void doBeforeInServiceLayer(JoinPoint joinPoint) {
        startTimeMillis = System.currentTimeMillis(); // 记录方法开始执行的时间  
    }  
  
    /** 
     * @Description: 方法调用后触发  记录结束时间 
     * @param joinPoint 
     */  
    @After("execution(* com.csun.dsms.controller..*.*(..))")
    public void doAfterInServiceLayer(JoinPoint joinPoint) {

        endTimeMillis = System.currentTimeMillis(); // 记录方法执行完成的时间

        this.printOptLog(joinPoint);
    }
  
    /** 
     * @Description: 环绕触发  
     * @param pjp 
     */  
    @Around("execution(* com.csun.dsms.controller..*.*(..))")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        /** 
         * 1.获取request信息 
         * 2.根据request获取session 
         * 3.从session中取出登录用户信息 
         */  
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
        HttpServletRequest request = sra.getRequest();
        //从header中得到token
        authorization = request.getHeader(GlobalConstant.AUTHORIZATION);

        // 获取输入参数  
        inputParamMap = request.getParameterMap();
        //System.out.println("userid = " + request.getParameter("userid"));
        // 获取请求地址  
        requestPath = request.getRequestURI();
        // 请求ip地址
        ipAddr = request.getRemoteAddr();
          
        // 执行完方法的返回值：调用proceed()方法，就会触发切入点方法执行  
        outputParamMap = new HashMap<String, Object>();  
        Object result = pjp.proceed();// result的值就是被拦截方法的返回值  
        outputParamMap.put("result", result);  
          
        return result;  
    }  
  
    /** 
     * @Description: 输出日志  
     */  
    private void printOptLog(JoinPoint joinPoint) {

        Gson gson = new Gson(); // 需要用到google的gson解析包
        String optTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
        						.format(startTimeMillis);

        HttpServletRequest request = RequestUtil.getRequest();
        final UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));

        logger.info("\n " + optTime + "; authorization：" + authorization  
                + "; url：" + requestPath
                + "; url：" + ipAddr
                + "; pro_time：" + (endTimeMillis - startTimeMillis) + "ms"  
                + "; param：" + gson.toJson(inputParamMap) + ";" 
                + "\n result："
                + gson.toJson(outputParamMap));

        LogAnnotation relog = giveController(joinPoint);
        if (relog == null) {
            return;
        }

        DsmsUser user = (DsmsUser) redisUtil.get(authorization);
        if (PublicUtil.isEmpty(user)) {
            return ;
        }

        //获取客户端操作系统
        final String os = userAgent.getOperatingSystem().getName();
        //获取客户端浏览器
        final String browser = userAgent.getBrowser().getName();
        String requestURI = request.getRequestURI();

        DsmsLog dsmsLog = new DsmsLog();
        dsmsLog.setClassName(joinPoint.getTarget().getClass().getName());
        dsmsLog.setMethodName(joinPoint.getSignature().getName());
        dsmsLog.setExcuteTime(endTimeMillis - startTimeMillis);
        dsmsLog.setStartTime(new Date(startTimeMillis));
        dsmsLog.setEndTime(new Date(endTimeMillis));
        dsmsLog.setIp(ipAddr);
        dsmsLog.setOs(os);
        dsmsLog.setBrowser(browser);
        dsmsLog.setRequestUrl(requestURI);
        dsmsLog.setLogType(relog.logType().getType());
        dsmsLog.setLogName(relog.logType().getName());

        getControllerMethodDescription(relog, dsmsLog, outputParamMap.get("result"), joinPoint);

        taskExecutor.execute(() -> dsmsLogService.save(dsmsLog, user));
    }

    private static LogAnnotation giveController(JoinPoint joinPoint) {
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        String methodName = joinPoint.getSignature().getName();
        if (null != methods && 0 < methods.length) {
            for (Method met : methods) {
                LogAnnotation relog = met.getAnnotation(LogAnnotation.class);
                if (null != relog && methodName.equals(met.getName())) {
                    return relog;
                }
            }
        }

        return null;
    }

    private void getControllerMethodDescription(LogAnnotation relog, DsmsLog dsmsLog, Object result, JoinPoint joinPoint) {

        if (relog.isSaveRequestData()) {
            setRequestData(dsmsLog, joinPoint);
        }
        if (relog.isSaveResponseData()) {
            setResponseData(dsmsLog, result);
        }
    }

    private void setResponseData(DsmsLog requestLog, Object result) {
        try {
            requestLog.setResponseData(String.valueOf(result));
        } catch (Exception e) {
            log.error("获取响应数据,出现错误={}", e.getMessage(), e);
        }
    }

    private void setRequestData(DsmsLog uacLog, JoinPoint joinPoint) {

        try {
            Object[] args = joinPoint.getArgs();
            if (args.length == 0) {
                return;
            }
            Object[] parameter = new Object[args.length];
            int index = 0;
            for (Object object : args) {
                if (object instanceof HttpServletRequest) {
                    continue;
                }
                // 不记录bean property binding result
                if (object instanceof BeanPropertyBindingResult) {
                    continue;
                }
                parameter[index] = object;
                index++;
            }

            String requestData = JacksonUtil.toJsonWithFormat(parameter);

            if (requestData.length() > MAX_SIZE) {
                requestData = requestData.substring(MAX_SIZE);
            }

            uacLog.setRequestData(requestData);
        } catch (Exception e) {
            log.error("获取响应数据,出现错误={}", e.getMessage(), e);
        }
    }
	
}
